#include <iostream>
#include <math.h>
#include <limits.h>
#include "ALU_DEF.h"
#include "ALU_ADD.h"

using namespace std;

ALU_ADD::ALU_ADD()
{
	Ready = true;
	inputA = 0;
	inputFA = 0.0f;
	inputB = 0;
	inputFB = 0.0f;
	outputZ = 0;
	outputFZ = 0.0f;
	OpCode = NOP_OPCODE;
	CyclesLeft = 0;
}

void ALU_ADD::update()
{
	long long int temp = 0;
	unsigned long long int temp2= 0;
	unsigned int temp3 = 0;

	if(Ready)
	{
		if( NOP_OPCODE != OpCode )
		{
			switch (OpCode)
			{
			case IADD_OPCODE:
				CyclesLeft = IADD_DELAY;
				break;
			case ISUB_OPCODE:
				CyclesLeft = ISUB_DELAY;
				break;
			case IABD_OPCODE:
				CyclesLeft = IABD_DELAY;
				break;
			case UABD_OPCODE:
				CyclesLeft = UABD_DELAY;
				break;
			case ILT_OPCODE:
				CyclesLeft = ILT_DELAY;
				break;
			case ULT_OPCODE:
				CyclesLeft = ULT_DELAY;
				break;
			case ILE_OPCODE:
				CyclesLeft = ILE_DELAY;
				break;
			case ULE_OPCODE:
				CyclesLeft = ULE_DELAY;
				break;
			case IEQ_OPCODE:
				CyclesLeft = IEQ_DELAY;
				break;
			case NEQ_OPCODE:
				CyclesLeft = NEQ_DELAY;
				break;
			case AND_OPCODE:
				CyclesLeft = AND_DELAY;
				break;
			case OR_OPCODE:
				CyclesLeft = OR_DELAY;
				break;
			case XOR_OPCODE:
				CyclesLeft = XOR_DELAY;
				break;
			case NOT_OPCODE:
				CyclesLeft = NOT_DELAY;
				break;
			case IADDS_OPCODE:
				CyclesLeft = IADDS_DELAY;
				break;
			case UADDS_OPCODE:
				CyclesLeft = UADDS_DELAY;
				break;
			case ISUBS_OPCODE:
				CyclesLeft = ISUBS_DELAY;
				break;
			case USUBS_OPCODE:
				CyclesLeft = USUBS_DELAY;
				break;
			case SHIFT_OPCODE:
				CyclesLeft = SHIFT_DELAY;
				break;
			case SHIFTA_OPCODE:
				CyclesLeft = SHIFT_DELAY;
				break;
			case ROTATE_OPCODE:
				CyclesLeft = ROTATE_DELAY;
				break;
			case FADD_OPCODE:
				CyclesLeft = FADD_DELAY;
				break;
			case FSUB_OPCODE:
				CyclesLeft = FSUB_DELAY;
				break;
			case FABS_OPCODE:
				CyclesLeft = FABS_DELAY;
				break;
			case FLT_OPCODE:
				CyclesLeft = FLT_DELAY;
				break;
			case FLE_OPCODE:
				CyclesLeft = FLE_DELAY;
				break;
			case FTOI_OPCODE:
				CyclesLeft = FTOI_DELAY;
				break;
			case FFRAC_OPCODE:
				CyclesLeft = FFRAC_DELAY;
				break;
			case ITOF_OPCODE:
				CyclesLeft = ITOF_DELAY;
				break;
			}

			Ready = false;
			update();
		}
	}
	else // Not ready, processing operation
	{
		CyclesLeft--;

		if( 0 == CyclesLeft )
		{
			switch( OpCode )
			{
				case IADD_OPCODE:
					outputZ = inputA + inputB;
					break;
				case ISUB_OPCODE:
					outputZ = inputA - inputB;
					break;
				case IABD_OPCODE:
					outputZ = abs(inputA - inputB);
					break;
				case UABD_OPCODE:
					outputZ = abs((int) ((unsigned int) inputA - (unsigned int) inputB));
					break;
				case ILT_OPCODE:
					outputZ = (inputA < inputB);
					break;
				case ULT_OPCODE:
					outputZ = ((unsigned int) inputA < (unsigned int) inputB);
					break;
				case ILE_OPCODE:
					outputZ = (inputA <= inputB);
					break;
				case ULE_OPCODE:
					outputZ = ((unsigned int) inputA <= (unsigned int) inputB);
					break;
				case IEQ_OPCODE:
					outputZ = (inputA == inputB);
					break;
				case NEQ_OPCODE:
					outputZ = (inputA != inputB);
					break;
				case AND_OPCODE:
					outputZ = inputA & inputB;
					break;
				case OR_OPCODE:
					outputZ = inputA | inputB;
					break;
				case XOR_OPCODE:
					outputZ = inputA ^ inputB;
					break;
				case NOT_OPCODE:
					outputZ = !inputA;
					break;
				case IADDS_OPCODE:
					temp = (long long int) inputA + (long long int) inputB;
					if (temp > INT_MAX)
						outputZ = INT_MAX;
					else if (temp < INT_MIN)
						outputZ = INT_MIN;
					else
						outputZ = inputA + inputB;
					break;
				case UADDS_OPCODE:
					temp2 = (unsigned long long int) inputA + (unsigned long long int) inputB;
					if (temp2 > UINT_MAX)
						outputZ = UINT_MAX;
					else
						outputZ = (unsigned int) inputA + (unsigned int) inputB;
					break;
				case ISUBS_OPCODE:
					temp = (long long int) inputA - (long long int) inputB;
					if (temp > INT_MAX)
						outputZ = INT_MAX;
					else if (temp < INT_MIN)
						outputZ = INT_MIN;
					else
						outputZ = inputA - inputB;
					break;
				case USUBS_OPCODE:
					temp = (unsigned long long int) inputA - (unsigned long long int) inputB;
					if (temp < 0)
						outputZ = 0;
					else
						outputZ = inputA - inputB;
					break;
				case SHIFT_OPCODE:
					outputZ = (unsigned) inputA >> inputB;		// May not work in all compilers
					break;
				case SHIFTA_OPCODE:
					outputZ = inputA >> inputB;					// Works for VISUAL STUDIOS
					break;
				case ROTATE_OPCODE:
					outputZ = (((unsigned int) inputA >> (unsigned int) inputB) | (unsigned int) inputA << (32-(unsigned int) inputB));
					break;
				case FADD_OPCODE:
					outputFZ = inputFA + inputFB;
					break;
				case FSUB_OPCODE:
					outputFZ = inputFA - inputFB;
					break;
				case FABS_OPCODE:
					outputFZ = abs(inputFA);
					break;
				case FLT_OPCODE:
					outputZ = inputFA < inputFB;
					break;
				case FLE_OPCODE:
					outputZ = inputFA <= inputFB;
					break;
				case FTOI_OPCODE:
					outputZ = (int) inputFA;
					break;
				case FFRAC_OPCODE:
					outputFZ = inputFA - floor(inputFA);
					break;
				case ITOF_OPCODE:
					outputFZ = (float) inputA;
					break;
			}

			Ready = true;
		}
	}
}

int ALU_ADD::getOutput()
{
	return outputZ;
}

float ALU_ADD::getOutputF()
{
	return outputFZ;
}

bool ALU_ADD::isReady()
{
	return Ready;
}
